/**
 * HexGridUtils.java
 *
 * Roslan's Remake of MOO in 3D using Ardor3D
 * (C) 2010-2012 Roslan Amir. All rights reserved.
 */

package name.roslan.games.moo3d.utils;

import java.awt.Point;

import com.ardor3d.math.FastMath;
import com.ardor3d.math.MathUtils;
import com.ardor3d.math.Vector2;
import com.ardor3d.math.Vector3;

/**
 * Utility methods for the hex grid.
 * 
 * @author Roslan Amir
 * @version 1.0 - Jan 5, 2011
 */
public class HexGridUtils {

	/**
	 * 
	 */
	public static final double HEXGRID_S = 1.0;

	/**
	 * 
	 */
	public static final double HEXGRID_H = FastMath.sin(30 * MathUtils.DEG_TO_RAD) * HEXGRID_S;

	/**
	 * 
	 */
	public static final double HEXGRID_R = FastMath.cos(30 * MathUtils.DEG_TO_RAD) * HEXGRID_S;

	/**
	 * 
	 */
	// public static final double HEXGRID_A = HEXGRID_R * 2;

	/**
	 * 
	 */
	// public static final double HEXGRID_B = HEXGRID_S + HEXGRID_H * 2;

	/**
	 * Calculates the origin as <code>Vector2</code> in world coordinates for the grid tile given the hex coordinates (x,y).
	 * 
	 * @param x - the x coordinate
	 * @param y - the y coordinate
	 * @param origin - the Vector2 to store the result in. Must not be null.
	 */
	public static void tileToVector2(double x, double y, Vector2 origin) {
		// if (origin == null)
		// throw new IllegalArgumentException();
		origin.set((x - y) * HEXGRID_R, (x + y) * (HEXGRID_S + HEXGRID_H));
	}

	/**
	 * Calculates the origin  as <code>Vector2</code> in world coordinates for the grid tile given the hex coordinates (x,y).
	 * 
	 * @param tile - the coordinates
	 * @param origin - the Vector2 to store the result in. Must not be null.
	 */
	public static void tileToVector2(Point tile, Vector2 origin) {
		tileToVector2(tile.x, tile.y, origin);
	}

	/**
	 * Calculates the origin  as <code>Vector3</code> in world coordinates for the grid tile given the hex coordinates (x,y).
	 * 
	 * @param tile - the coordinates
	 * @param origin - the Vector3 to store the result in. Must not be null.
	 */
	public static void tileToVector3(Point tile, Vector3 origin) {
		tileToVector3(tile.x, tile.y, origin);
	}

	/**
	 * Calculates the origin as <code>Vector3</code> in world coordinates for the grid tile given the hex coordinates (x,y).
	 * 
	 * @param x - the x coordinate
	 * @param y - the y coordinate
	 * @param origin - the Vector3 to store the result in. Must not be null.
	 */
	public static void tileToVector3(double x, double y, Vector3 origin) {
		// if (origin == null)
		// throw new IllegalArgumentException();
		origin.set((x - y) * HEXGRID_R, (x + y) * (HEXGRID_S + HEXGRID_H), 0.0);
	}

	/**
	 * Calculates the array of vertices to draw the full hex.
	 * 
	 * @param tuples - the array to store the result in. If null, a new array is created and returned.
	 */
	public static void getAllSegmentVertices6(Vector2[] tuples) {
		// if (tuples == null)
		// throw new IllegalArgumentException();
		tuples[0].set(0, -(HEXGRID_S / 2 + HEXGRID_H));
		tuples[1].set(-HEXGRID_R, -HEXGRID_S / 2);
		tuples[2].set(-HEXGRID_R, HEXGRID_S / 2);
		tuples[3].set(0, HEXGRID_S / 2 + HEXGRID_H);
		tuples[4].set(HEXGRID_R, HEXGRID_S / 2);
		tuples[5].set(HEXGRID_R, -HEXGRID_S / 2);
	}

	/**
	 * Return the array of vertices to draw the lower-right, lower-left and left 3-line segments of a hex grid.
	 * 
	 * @param tuples - the array to store the result in. If null, a new array is created and returned.
	 */
	public static void getLowerRightLowerLeftLeftSegmentVertices4(Vector2[] tuples) {
		// if (tuples == null)
		// throw new IllegalArgumentException();
		tuples[0].set(HEXGRID_R, -HEXGRID_S / 2);
		tuples[1].set(0, -HEXGRID_S / 2 - HEXGRID_H);
		tuples[2].set(-HEXGRID_R, -HEXGRID_S / 2);
		tuples[3].set(-HEXGRID_R, HEXGRID_S / 2);
	}

	/**
	 * Return the array of vertices to draw the upper-right and right 2-line segments of a hex grid.
	 * 
	 * @param tuples - the array to store the result in. If null, a new array is created and returned.
	 */
	public static void getUpperRightRightSegmentVertices3(Vector2[] tuples) {
		// if (tuples == null)
		// throw new IllegalArgumentException();
		tuples[0].set(0, HEXGRID_S / 2 + HEXGRID_H);
		tuples[1].set(HEXGRID_R, HEXGRID_S / 2);
		tuples[2].set(HEXGRID_R, -HEXGRID_S / 2);
	}

	/**
	 * Comment for method.
	 * 
	 * @param tuples - the array to store the result in. If null, a new array is created and returned.
	 */
	public static void getLowerLeftLeftSegmentVertices3(Vector2[] tuples) {
		// if (tuples == null)
		// throw new IllegalArgumentException();
		tuples[0].set(0, -(HEXGRID_S / 2 + HEXGRID_H));
		tuples[1].set(-HEXGRID_R, -HEXGRID_S / 2);
		tuples[2].set(-HEXGRID_R, HEXGRID_S / 2);
	}

	/**
	 * Comment for method.
	 * 
	 * @param tuples - the array to store the result in. If null, a new array is created and returned.
	 */
	public static void getLeftUpperLeftSegmentVertices3(Vector2[] tuples) {
		// if (tuples == null)
		// throw new IllegalArgumentException();
		tuples[0].set(-HEXGRID_R, -HEXGRID_S / 2);
		tuples[1].set(-HEXGRID_R, HEXGRID_S / 2);
		tuples[2].set(0, HEXGRID_S / 2 + HEXGRID_H);
	}

	/**
	 * Comment for method.
	 * 
	 * @param tuples - the array to store the result in. If null, a new array is created and returned.
	 */
	public static void getUpperLeftUpperRightSegmentVertices3(Vector2[] tuples) {
		// if (tuples == null)
		// throw new IllegalArgumentException();
		tuples[0].set(-HEXGRID_R, HEXGRID_S / 2);
		tuples[1].set(0, HEXGRID_S / 2 + HEXGRID_H);
		tuples[2].set(HEXGRID_R, HEXGRID_S / 2);
	}

	/**
	 * Comment for method.
	 * 
	 * @param tuples - the array to store the result in. If null, a new array is created and returned.
	 */
	public static void getRightLowerRightSegmentVertices3(Vector2[] tuples) {
		// if (tuples == null)
		// throw new IllegalArgumentException();
		tuples[0].set(HEXGRID_R, HEXGRID_S / 2);
		tuples[1].set(HEXGRID_R, -HEXGRID_S / 2);
		tuples[2].set(0, -(HEXGRID_S / 2 + HEXGRID_H));
	}

	/**
	 * Comment for method.
	 * 
	 * @param tuples - the array to store the result in. If null, a new array is created and returned.
	 */
	public static void getLowerRightLowerLeftSegmentVertices3(Vector2[] tuples) {
		// if (tuples == null)
		// throw new IllegalArgumentException();
		tuples[0].set(HEXGRID_R, -HEXGRID_S / 2);
		tuples[1].set(0, -(HEXGRID_S / 2 + HEXGRID_H));
		tuples[2].set(-HEXGRID_R, -HEXGRID_S / 2);
	}

	/**
	 * Return the array of vertices to draw the right 1-line segment of a hex grid.
	 * 
	 * @param tuples - the array to store the result in. If null, a new array is created and returned.
	 * @return the array of line segments
	 */
	public static void getRightSegmentVertices2(Vector2[] tuples) {
		// if (tuples == null)
		// throw new IllegalArgumentException();
		tuples[0].set(HEXGRID_R, HEXGRID_S / 2);
		tuples[1].set(HEXGRID_R, -HEXGRID_S / 2);
	}

	/**
	 * Return the array of vertices to draw the upper-left 1-line segment of a hex grid.
	 * 
	 * @param tuples - the array to store the result in. If null, a new array is created and returned.
	 */
	public static void getUpperLeftSegmentVertices2(Vector2[] tuples) {
		// if (tuples == null)
		// throw new IllegalArgumentException();
		tuples[0].set(-HEXGRID_R, HEXGRID_S / 2);
		tuples[1].set(0, HEXGRID_S / 2 + HEXGRID_H);
	}

	/**
	 * Comment for method.
	 * 
	 * @param tuples - the array to store the result in. If null, a new array is created and returned.
	 */
	public static void getLowerRightSegmentVertices2(Vector2[] tuples) {
		// if (tuples == null)
		// throw new IllegalArgumentException();
		tuples[0].set(HEXGRID_R, -HEXGRID_S / 2);
		tuples[1].set(0, -(HEXGRID_S / 2 + HEXGRID_H));
	}

	/**
	 * Comment for method.
	 * 
	 * @param tuples - the array to store the result in. If null, a new array is created and returned.
	 */
	public static void getLowerLeftSegmentVertices2(Vector2[] tuples) {
		// if (tuples == null)
		// throw new IllegalArgumentException();
		tuples[0].set(0, -(HEXGRID_S / 2 + HEXGRID_H));
		tuples[1].set(-HEXGRID_R, -HEXGRID_S / 2);
	}

	/**
	 * Comment for method.
	 * 
	 * @param tuples - the array to store the result in. If null, a new array is created and returned.
	 */
	public static void getUpperRightSegmentVertices2(Vector2[] tuples) {
		// if (tuples == null)
		// throw new IllegalArgumentException();
		tuples[0].set(0, HEXGRID_S / 2 + HEXGRID_H);
		tuples[1].set(HEXGRID_R, HEXGRID_S / 2);
	}

	/**
	 * Comment for method.
	 * 
	 * @param tuples - the array to store the result in. If null, a new array is created and returned.
	 */
	public static void getLeftSegmentVertices2(Vector2[] tuples) {
		// if (tuples == null)
		// throw new IllegalArgumentException();
		tuples[0].set(-HEXGRID_R, -HEXGRID_S / 2);
		tuples[1].set(-HEXGRID_R, HEXGRID_S / 2);
	}

	/**
	 * Calculate the distance (in integral value) between the two tiles.
	 * 
	 * @param from - the coordinates of the starting grid tile
	 * @param to - the coordinates of the ending grid tile
	 * @return the distance between the two tiles with the distance between 2 adjacent tiles defined as 1
	 */
	public static int distance(Point from, Point to) {
		int dx = to.x - from.x;
		int dy = to.y - from.y;
		if ((dx & 0x80000000) != (dy & 0x80000000))
			return Math.max(Math.abs(dx), Math.abs(dy));
		else
			return Math.abs(dx) + Math.abs(dy);
	}

	/**
	 * Returns the list of hex coordinates for the path between the starting hex and the ending hex. The list does not include the starting hex coordinates.
	 * 
	 * @param start - the starting hex coordinates
	 * @param end - the ending hex coordinates
	 * @return the array of <code>Point</code>s representing the coordinates for the hexes along the way
	 */
	public static Point[] path(Point start, Point end) {
		/*
		 * Returns if start and end nodes are the same
		 */
		if (start.x == end.x && start.y == end.y) {
			return null;
		}

		final Point[] pathList = new Point[distance(start, end)];

		/*
		 * along diagonal axis (lower-left to upper-right or vice-versa)
		 */
		if (start.y == end.y) {
			int index = 0;
			if (start.x < end.x) { // lower-left to upper-right
				for (int x = start.x + 1; x < end.x; x++) {
					pathList[index++] = new Point(x, start.y);
				}
			} else { // upper-right to lower-left
				for (int x = start.x - 1; x > end.x; x--) {
					pathList[index++] = new Point(x, start.y);
				}
			}
			pathList[index] = new Point(end.x, end.y);
			return pathList;
		}

		/*
		 * along diagonal axis (lower-right to upper-left or vice-versa)
		 */
		if (start.x == end.x) {
			int index = 0;
			if (start.y < end.y) { // lower-right to upper-left
				for (int y = start.y + 1; y < end.y; y++) {
					pathList[index++] = new Point(start.x, y);
				}
			} else { // upper-left to lower-right
				for (int y = start.y - 1; y > end.y; y--) {
					pathList[index++] = new Point(start.x, y);
				}
			}
			pathList[index] = new Point(end.x, end.y);
			return pathList;
		}

		/*
		 * along horizontal axis (left to right or vice-versa)
		 */
		if ((start.x + start.y) == (end.x + end.y)) {
			int index = 0;
			if (start.x < end.x) { // left to right
				for (int x = start.x + 1, y = start.y - 1; x < end.x; x++, y--) {
					pathList[index++] = new Point(x, y);
				}
			} else { // Right to left
				for (int x = start.x - 1, y = start.y + 1; x > end.x; x--, y++) {
					pathList[index++] = new Point(x, y);
				}
			}
			pathList[index] = new Point(end.x, end.y);
			return pathList;
		}

		/*
		 * horizontal-biased (first & second quadrants)
		 */
		if (start.x < end.x && start.y > end.y) {
			int doubleDx = Math.abs((end.x - start.x) + (start.y - end.y)); // = 2 * dx
			int dy = (end.x - start.x) + (end.y - start.y);
			boolean yPositive = dy > 0;
			int thetaX = 3 * doubleDx; // Math.abs(doubleDx);
			int thetaY = 3 * Math.abs(dy);

			int x = start.x;
			int y = start.y;
			int deviation = 0;
			int index = 0;
			while (x != end.x || y != end.y) {
				deviation += thetaY;
				if (deviation > doubleDx) {
					if (yPositive) {
						x++; // upper-right: x = x + 1, y stays the same
					} else {
						y--; // lower-right: x stays the same, y = y - 1
					}
					deviation -= thetaX;
				} else {
					x++;
					y--; // right: x = x + 1, y = y - 1
					deviation += thetaY;
				}
				pathList[index++] = new Point(x, y);
			}
			return pathList;
		}

		/*
		 * horizontal-biased (third & fourth quadrants)
		 */
		if (start.x > end.x && start.y < end.y) {
			int doubleDx = Math.abs((end.x - start.x) + (start.y - end.y)); // = 2 * dx
			int dy = (end.x - start.x) + (end.y - start.y);
			boolean yPositive = dy > 0;
			int thetaX = 3 * doubleDx; // Math.abs(doubleDx);
			int thetaY = 3 * Math.abs(dy);

			int x = start.x;
			int y = start.y;
			int deviation = 0;
			int index = 0;
			while (x != end.x || y != end.y) {
				deviation += thetaY;
				if (deviation > doubleDx) {
					if (yPositive) {
						y++; // upper-left: x stays the same, y = y + 1
					} else {
						x--; // lower-left: x = x - 1, y stays the same
					}
					deviation -= thetaX;
				} else {
					x--;
					y++; // left: x = x - 1, y = y + 1
					deviation += thetaY;
				}
				pathList[index++] = new Point(x, y);
			}
			return pathList;
		}

		/*
		 * vertical-biased (quadrants 1 & 4) (dy is always positive)
		 */
		if (start.x < end.x && start.y < end.y) {
			int doubleDx = (end.x - start.x) + (start.y - end.y); // = 2 * dx
			boolean xPositive = doubleDx > 0;
			int thetaX = Math.abs(doubleDx);

			int dy = (end.x - start.x) + (end.y - start.y);
			int thetaY = Math.abs(dy);

			int x = start.x;
			int y = start.y;
			int deviation = 0;
			int index = 0;
			while (x != end.x || y != end.y) {
				deviation += thetaX;
				if (deviation > 0) {
					// current hex = get DIRS[Xsign][Ysign] hex
					if (xPositive) {
						x++; // upper-right: x = x + 1, y stays the same
					} else {
						y++; // upper-left: x stays the same, y = y + 1
					}
					deviation -= thetaY;
				} else {
					// current hex = get DIRS[-Xsign][Ysign] hex
					if (xPositive) {
						y++; // upper-left: x stays the same, y = y + 1
					} else {
						x++; // upper-right: x = x + 1, y stays the same
					}
					deviation += thetaY;
				}
				pathList[index++] = new Point(x, y);
			}
			return pathList;
		}

		/*
		 * vertical-biased (quadrants 2 & 3) (dy is always negative)
		 */
		if (start.x > end.x && start.y > end.y) {
			int doubleDx = (end.x - start.x) + (start.y - end.y); // = 2 * dx
			boolean xPositive = doubleDx > 0;
			int thetaX = Math.abs(doubleDx);

			int dy = (end.x - start.x) + (end.y - start.y);
			int thetaY = Math.abs(dy);

			int x = start.x;
			int y = start.y;
			int deviation = 0;
			int index = 0;
			while (x != end.x || y != end.y) {
				deviation += thetaX;
				if (deviation > 0) {
					// current hex = get DIRS[Xsign][Ysign] hex
					if (xPositive) {
						y--; // lower-right: x stays the same, y = y - 1
					} else {
						x--; // lower-left: x = x - 1, y stays the same
					}
					deviation -= thetaY;
				} else {
					// current hex = get DIRS[-Xsign][Ysign] hex
					if (xPositive) {
						x--; // lower-left: x = x - 1, y stays the same
					} else {
						y--; // lower-right: x stays the same, y = y - 1
					}
					deviation += thetaY;
				}
				pathList[index++] = new Point(x, y);
			}
			return pathList;
		}

		return null;
	}

	/**
	 * Return the list of hex coordinates of the tiles representing the outer perimeter of the circle given the radius. The number of hexes that form the
	 * perimeter of the circle is 6 times the radius.
	 * 
	 * @param radius - the radius of the circle in hex tiles
	 * @return the list of coordinates in an array of <code>Point</code>s
	 */
	public static Point[] getCircleHexes(int radius) {
		if (radius < 1)
			throw new IllegalArgumentException("Invalid radius: " + radius);

		Point[] hexes = new Point[radius * 6];
		int index = 0;
		for (int i = 0, x = -radius, y = 0; i < radius; i++, y++) {
			hexes[index++] = new Point(x, y);
		}
		for (int i = 0, x = -radius, y = radius; i < radius; i++, x++) {
			hexes[index++] = new Point(x, y);
		}
		for (int i = 0, x = 0, y = radius; i < radius; i++, x++, y--) {
			hexes[index++] = new Point(x, y);
		}
		for (int i = 0, x = radius, y = 0; i < radius; i++, y--) {
			hexes[index++] = new Point(x, y);
		}
		for (int i = 0, x = radius, y = -radius; i < radius; i++, x--) {
			hexes[index++] = new Point(x, y);
		}
		for (int i = 0, x = 0, y = -radius; i < radius; i++, x--, y++) {
			hexes[index++] = new Point(x, y);
		}

		return hexes;
	}

}
